package sagroup.codegen;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.Properties;

import lime2.LimeConstants;

/**
 * Auto generate code of ?.java from ?Def.txt. The command parameters are
 * like:.\src\lime2 TupleType 2 parameters respectively inform the path where is
 * the location of .txt and .java and class name. please refer to *Def.txt for
 * the format of the definition files.
 * 
 * @author Wenhui Pan
 * 
 */
public class GenWrapper {
	private static FieldProperty[] fields;
	private static String className;

	public static void main(String[] args) throws IOException {
		if (args.length == 2) {
			run(args[0], args[1]);
		} else {
			System.out
					.println("Please input parameters such as:.\\src\\lime2\\ XTVR");
			BufferedReader br = new BufferedReader(new InputStreamReader(
					System.in));
			String[] paras = br.readLine().split(" ");
			className = paras[1] + "Wrapper";
			run(paras[0], paras[1]);
		}

	}

	static class FieldProperty {
		String varName;
		String className;
		boolean isPrimitive;
		boolean canBeNull;
		Integer position;
		String positionID;
		String isWild;
		String wildCard;

		FieldProperty(String varName, String varProperty) {
			String[] ps = varProperty.split(" ");
			this.varName = varName;
			int i = 0;
			this.className = ps[i++];
			this.isPrimitive = Boolean.parseBoolean(ps[i++]);
			this.canBeNull = Boolean.parseBoolean(ps[i++]);
			this.position = Integer.valueOf(ps[i++]);
			this.positionID = (ps[i++]);
			this.isWild = ps[i++];
			this.wildCard = ps[i++];
		}

		//TODO: even for non primitive, can simplify code to same as primitive.
		public StringBuffer genToString() {
			StringBuffer sb = new StringBuffer();
			sb.append("\" ");
			sb.append(varName);
			if (!isPrimitive) {
				sb.append("=\" + (");
				sb.append(varName);
				sb.append(" == null ? \"null\" : ");
				sb.append(varName);
				sb.append(".toString())");
			} else {
				sb.append("=\" + ");
				sb.append(varName);
			}
			return sb;
		}

		StringBuffer genConstructorPara() {
			return new StringBuffer().append(className + " " + varName);
		}

		StringBuffer genConstructorBody() {
			StringBuffer sb = new StringBuffer();
			sb.append("this." + varName + "=" + varName + ";\n");
			return sb;
		}

		StringBuffer genDeTuplefy() {
			StringBuffer sb = new StringBuffer();
			sb.append(String.format(
					"f = (IValuedField) flat.get(%1$s);\n if (f.isFormal()){%2$s = "
							+ wildCard
							+ ";}\nelse{\n	%2$s = (%3$s) f.getValue();}\n",
					new Object[] { positionID, varName, className }));
			return sb;
		}

		StringBuffer genTuplefy() {
			StringBuffer sb = new StringBuffer();
			sb.append(String.format("if (%1$s" + isWild + ") { \n"
					+ "t.insertAt(new Field().setType(" + "%2$s" + ".class),"
					+ "%3$s" + ");\n}", new Object[] { varName, className,
					positionID }));
			sb.append(String.format("else{\n"
					+ "t.insertAt(new Field().setValue(" + "%1$s" + "),"
					+ "%2$s" + ");\n}", new Object[] { varName, positionID }));
			return sb;
		}

		public StringBuffer genDefAndAccesser() {
			StringBuffer sb = new StringBuffer();
			// sb.append("private ");
			sb.append(className);
			sb.append(" ");
			sb.append(varName);
			sb.append(";\n");

			// getter
			// sb.append("public ");
			// sb.append(className);
			// sb.append(" get");
			// String capitalizedFirst = new String(varName.substring(0, 1))
			// .toUpperCase();
			// sb.append(capitalizedFirst + varName.substring(1));
			// sb.append("(){\n");
			// sb.append("return ");
			// sb.append(varName);
			// sb.append(";\n}\n");
			return sb;
		}

		public StringBuffer genFinalConstants() {
			StringBuffer sb = new StringBuffer();
			sb
					.append("static final int " + positionID + "=" + position
							+ ";\n");
			sb.append("static final int " + positionID + "_4_LIME" + "="
					+ positionID + "+ LimeConstants.TAGS_NUMBER" + ";\n");
			return sb;
		}

	}

	private static void run(String path, String pp) {
		// Read properties file.
		Properties properties = new Properties();
		try {
			properties.load(new FileInputStream(path + "\\" + pp
					+ "Def.properties"));
			Enumeration en = properties.propertyNames();
			fields = new FieldProperty[properties.size()];
			int index = 0;

			while (en.hasMoreElements()) {
				String key = (String) en.nextElement();
				String Property = properties.getProperty(key);
				fields[index++] = new FieldProperty(key, Property);
				System.out.println(key + " = " + Property);
			}

			// because we should ensure the order of parametere in all generated
			// methods so we need below
			FieldProperty[] disorderedFields = fields;
			fields = new FieldProperty[properties.size()];
			for (int i = 0; i < fields.length; i++) {
				fields[disorderedFields[i].position.intValue()] = disorderedFields[i];
			}

			BufferedWriter bw = null;
			try {
				bw = new BufferedWriter(new FileWriter(path + className
						+ ".java"));
				bw.append("package lime2;\n");
				bw.append("class " + className
						+ " implements java.io.Serializable " + "{\n");
				bw.append(genFinalConstants());
				bw.append(genDefAndAccess());
				bw.append(genMisc());
				bw.append(genToString());
				bw.append("}\n");
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} finally {
				if (bw != null) {
					try {
						bw.close();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}

		} catch (IOException e) {
			e.printStackTrace();
		}

		// Write properties file.
		// try {
		// properties.store(new FileOutputStream("filename.properties"), null);
		// } catch (IOException e) {
		// }
	}

	private static StringBuffer genDefAndAccess() {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		for (i = 0; i < fields.length; i++) {
			sb.append(fields[i].genDefAndAccesser());
		}

		sb.append("ITuple tuple;\n");
		return sb;

	}

	private static StringBuffer genFinalConstants() {
		StringBuffer sb = new StringBuffer();
		int i = 0;
		for (i = 0; i < fields.length; i++) {
			sb.append(fields[i].genFinalConstants());
		}
		sb.append("\nstatic final int TAGS_NUMBER = " + i + ";\n");
		sb.append("static final int " + "TAGS_NUMBER_4_LIME" + "="
				+ "TAGS_NUMBER" + "+ LimeConstants.TAGS_NUMBER" + ";\n");
		return sb;
	}

	private static StringBuffer genMisc() {

		StringBuffer sb = new StringBuffer();
		sb.append("//TODO: deal specifation and exception.\n");

		// constructor header
		sb.append("public " + className + "(\n");
		for (int j = 0; j < fields.length; j++) {
			sb.append(fields[j].genConstructorPara());
			if (j != fields.length - 1) {
				sb.append(", ");
			} else {
				sb.append(", ");
			}
		}
		sb.append(" ITuple tuple");
		sb.append("){\n");
		// constructor body
		for (int j = 0; j < fields.length; j++) {
			sb.append(fields[j].genConstructorBody());
			if (j != fields.length - 1) {
			} else {
			}
		}
		sb.append("this.tuple = tuple;\n");
		sb.append("}\n");

		// tuplefy method
		// sb.append("public ITuple tuplefyWrapper() {\n");

		sb.append("/**@Modifies: nothing*/");
		sb.append("ITuple toTVRfiedTuple() {\n");
		sb.append("ITuple t = LimeTupleSpace.copy(tuple);\n");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].genTuplefy());
			sb.append("\n");
		}
		sb.append("return t;\n");
		sb.append("}\n");

		// deTuplefy method
		sb.append("/**@Modifies: nothing*/");
		sb.append("static " + className + " deTVRfy(ITuple flat) {\n");
		sb.append(className + " toReturn" + "= null;\n");
		for (int i = 0; i < fields.length; i++) {
			sb.append(String.format(" %1$s %2$s;\n", new Object[] {
					fields[i].className, fields[i].varName }));
		}
		sb.append("ITuple t;\n" + "IValuedField f;\n");
		// parameter check
		sb.append("if (flat != null");
		for (int i = 0; i < fields.length; i++) {
			sb
					.append(String.format(
							" && flat.get(%1$s).getType().equals(%2$s.class)",
							new Object[] { fields[i].positionID,
									fields[i].className }));
		}
		sb.append("){\n");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].genDeTuplefy());
		}
		sb
				.append("t = LimeTupleSpace.copy(flat, TAGS_NUMBER, flat.length());\n");

		sb.append("toReturn = new " + className + "(");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].varName + ",");
			if (i != fields.length - 1) {
			} else {
			}
		}
		sb.append("t");
		sb.append(");\n");
		sb.append("}\n");
		sb.append("return toReturn;\n");
		sb.append("}\n");

		return sb;

	}

	private static StringBuffer genToString() {
		// TODO: use StringBuffer in generated java.
		StringBuffer sb = new StringBuffer();
		sb.append("public String toString() {\n");
		sb.append("return \"[" + className + "]\" +");
		for (int i = 0; i < fields.length; i++) {
			sb.append(fields[i].genToString());
			if (i < fields.length) {
				sb.append(" +");
			}
		}
		sb.append("tuple");
		sb.append(";\n");
		sb.append("}\n");
		return sb;
	}

}
